perm filename MFNTRP.SAI[MF,DEK]16 blob
sn#651065 filedate 1982-04-06 generic text, type C, neo UTF8
COMMENT ⊗ VALID 00011 PAGES
C REC PAGE DESCRIPTION
C00001 00001
C00003 00002 entry begin comment The interpretive module of METAFONT.
C00007 00003 require "mfbase.sai" source_file
C00008 00004 Maintaining the input stacks: pushinput,popinput,initin,dumpcontext,inslist
C00014 00005 Accessing user's files: scanfilename, inputfile
C00020 00006 The basic input procedure getnext and its cousins gettok,getstring
C00032 00007 Dependency lists and the dumpdlist procedure
C00038 00008 Operations on dependency lists: simpl,entersym,add,simplify,neweq,dsvalue
C00050 00009 Expression scanning routines: scanprimary, scanterm, scanexp, getexp
C00061 00010 The path scanning routine (scanpath)
C00071 00011 internal procedure maincontrol # governs all the activities
C00100 ENDMK
C⊗;
entry; begin comment The interpretive module of METAFONT.
(It is wise to read the memory allocation sections of MFSYS
before delving very deeply into the following code.)
The purpose of these routines is to figure out the algebraic structure of a
user's METAFONT input, and to evaluate the formulas appropriately,
meanwhile calling on procedures of MFRAST to draw the corresponding symbols.
The routines are built around a low-level syntactic procedure "getnext",
which sets the value of two variables "curtype" and "curval" representing the
next token of the input. Higher level procedures recursively interpret these tokens
in a way that seems simple once you understand it.
In spite of getnext's fairly straightforward duty, it must have a
rather elaborate mechanism beneath it, to convert from character files to tokens.
This complexity is due in part to the fact that subroutines are stored away
as linked lists of tokens that are fed back through the scanner when a
subroutine is called. One subroutine may, of course, be calling another. Furthermore
we may at a given time be in the midst of reading input from several character
files and from the user's terminal. To handle these situations, METAFONT has
various stacks that hold information about any incomplete activities. These
stack record the current state of an implicitly recursive process, while "getnext"
itself has been coded nonrecursively.
The higher-level scanning and evaluation procedures are explicitly recursive. They
manipulate defined values and linear combinations of independent still-to-be-defined
values, in such a way that values of variables are defined whenever sufficient
information has been scanned.
;
require "MFHDR.SAI" source_file;
internal saf integer array mem[0:memsize-1] # dynamic list memory;
internal saf real array vmem[0:vmemsize-1] # two-word list memory;
internal integer curtype # the current type code appearing in the input;
internal real curval # the current value appearing in the input;
internal real cursize # the current pen size;
internal integer curpen # the current pen type;
define curvalint = ⊂memory[location(curval),integer]⊃ # curval regarded as integer;
define circlemodebit=⊂'10000000000⊃;
require "mfbase.sai" source_file;
comment Maintaining the input stacks: pushinput,popinput,initin,dumpcontext,inslist;
internal simp procedure pushinput # save current input status on the stacks;
if inptr≥stacksize then overflow(stacksize) else
begin inbufstack[inptr]←inbuf;
curbfstack[inptr]←curbuf;
filenmstack[inptr]←filename;
locstack[inptr]←loc;
recvrystack[inptr]←recovery;
inptr←inptr+1;
end;
internal simp procedure popinput # finish input level, restore the previous;
begin integer t;
inptr←inptr-1;
inbuf←inbufstack[inptr];
curbuf←curbfstack[inptr];
filename←filenmstack[inptr];
loc←locstack[inptr];
recovery←recvrystack[inptr];
end;
define crffbreak=1,ffbreak=2 # break table codes, see below;
internal integer brchar # break character stored by system input;
internal integer eof # end-of-file code stored by system input;
boolean noinput # suppress input (conditionally skipped text);
internal procedure initin # get TEX input system ready to start;
begin setbreak(crffbreak,'15&'14,null,"INA") # crffbreak will now read the
input up to and including a carriage return or page mark,
ignoring oldstyle line numbers;
setbreak(ffbreak,'14,null,"INS") # ffbreak is used only to read past a
file directory page, it goes up to the first page mark;
inptr←0 # set input stacks empty;
inbuf←curbuf←filename←null;
loc←recovery←0;
pagewarning←null;
cond←false;
noinput←false;
end;
internal string curfile # current input file name, set by dumpcontext;
internal integer curfpage,curfline # set by dumpcontext;
internal procedure dumpcontext # prints where the scanner is;
begin comment This procedure shows the top levels of input, omitting
tokenlists that are about to be flushed (since they were most likely
inserted with inslist), until coming to a level that is a character file;
label processtokens # go here to process tokenlist levels of input;
integer ptr,t,n; string firstline # first line of a pair of context lines;
if (n←dumpwindow)≤0 then n←32 # max number of chars to include in printout;
ptr←inptr;
inbufstack[ptr]←inbuf;
curbfstack[ptr]←curbuf;
filenmstack[ptr]←filename;
locstack[ptr]←loc;
recvrystack[ptr]←recovery;
processtokens: while recvrystack[ptr]<0 do
begin firstline←"<subroutine> ";
dumplist(-recvrystack[ptr],locstack[ptr]);
if length(tokstring[0])>n then firstline←firstline&"...";
firstline←firstline&tokstring[0][∞-n+1 to ∞];
print(nextline,firstline);
setprint("","O");
IFWAITS print(""&'12) # terminal gets <linefeed>;
ELSEC print(nextline&" ") ENDC # fake it;
setprint("","F"); print(nextline,
" "
[1 to length(firstline)]) # file gets a bunch of spaces;
setprint("","B") # resume printing both to file and terminal;
print(tokstring[1][1 to n]);
if length(tokstring[1])>n then print("...");
ptr←ptr-1;
end;
curfile←filenmstack[ptr];
curfpage←field(info,locstack[ptr]);
curfline←field(link,locstack[ptr]);
if curfile then firstline←"p."&cvs(curfpage)&",l."&cvs(curfline)&" "
else firstline←"(*) ";
if inbufstack[ptr] = '12 then t←2 else t←1 # ignore initial linefeed;
firstline←firstline&inbufstack[ptr][t to (∞-length(curbfstack[ptr]))];
print(nextline,firstline);
setprint("","O");
IFWAITS print(""&'12) # terminal gets <linefeed>;
ELSEC print(nextline&" ") ENDC # fake it;
setprint("","F"); print(nextline,
" "
[1 to length(firstline)]) # file gets a bunch of spaces;
setprint("","B") # resume printing both to file and terminal;
print(curbfstack[ptr]);
if curfile=0 and ptr then
begin comment this level is an online insertion;
ptr←ptr-1; go to processtokens;
end;
print(nextline);
end;
comment Accessing user's files: scanfilename, inputfile;
comment This page contains the most operating-system dependent aspects
of the METAFONT input system;
IFWAITS
internal saf string array fname[0:2] # file name, extension, and directory;
internal simp procedure scanfilename # sets up fname[0:2];
begin integer j,c;
fname[0]←fname[1]←fname[2]←null;
j←0;
while curbuf and chartype[curbuf]=space do c←lop(curbuf);
loop begin c←chartype[curbuf];
case c of begin
[pnt] j←1;
[lbrack] j←2;
[comma][wxy][rbrack][digit][letter] ;
else done
end;
fname[j]←fname[j]&lop(curbuf);
end;
end;
procedure inputfile;
begin comment "input" has just been scanned. This procedure scans the user's
file name, employing the appropriate operating system naming conventions,
then reads in the first line and feeds it to the input system;
integer chan;
label abort # if something goes wrong trying to read the file;
label try # go here to try and try again;
string flname;
integer pageno # number of pages successfully read;
define checkeof=⊂if eof then begin print(")");go to abort end⊃;
try: scanfilename;
if noinput then return;
if fname[1]=0 then fname[1]←".mf";
flname←fname[0]&fname[1]&fname[2];
open(chan←getchan,"DSK",0,if inptr=0 then 19 else 2, 0,
150,brchar,eof);
comment On the SAIL system, 19 buffers is the most efficient for disk files;
comment The lines read in must have at most 150 characters;
lookup(chan,flname,eof);
if eof and fname[2]=0 then lookup(chan,fname[0]&fname[1]&"[TEX,SYS]",eof);
if eof then
begin error("Lookup failed on file "&flname&
" (and also on "&fname[0]&fname[1]&"[TEX,SYS])");
release(chan); go to try;
end;
print(" (",flname);
pushinput # save present file status;
recovery←chan; filename←flname;
inbuf←input(chan,crffbreak) # get first line of file;
checkeof; print(" 1");
if equ(inbuf[1 to 9],"COMMENT ⊗") then
begin comment Skip TVedit directory page;
while brchar≠'14 and not eof do inbuf←input(chan,ffbreak);
checkeof;
inbuf←input(chan,crffbreak) # get first line of second page;
checkeof; print(" 2");
pageno←2;
end
else pageno←1;
while brchar='14 do
begin comment Ignore empty pages at beginning of file;
inbuf←input(chan,crffbreak); checkeof; pageno←pageno+1; print(" ",pageno);
end;
loc ← (pageno lsh infod) + 1 # line 1 of the current page;
if pausing and not_nonstop then
begin integer p # garbage bin;
if inbuf='12 then p←lop(inbuf);
if length(inbuf)=1 then inbuf←" "&inbuf;
print(nextline);
ptostr(0,inbuf[1 to ∞-1]) # show inbuf on screen;
inbuf←inchwl&inbuf[∞ to ∞];
end;
curbuf←inbuf;
comment Now define the output file name if it hasn't yet been defined;
if ofilname=0 then ofilname←fname[0];
return;
abort: release(chan);
popinput;
end;
ENDWAITS;
IFTENEX require "MFFIL.SAI" source_file; ENDTENEX
IFTOPS20 require "MFF20.SAI" source_file; ENDTOPS20
internal integer bchan # channel for binary input;
procedure binopen;
if not_nonstop then
begin string s;
loop begin print(nextline,"File for binput: ");
s←inchwl;
setprint(null,"F"); print(s,nextline); setprint(null,"B") # echo on ERRORS.TMP;
open(bchan←getchan,"DSK",8, 2, 0, 0, 0, eof);
lookup(bchan,s,eof);
if eof then print("Lookup failed on file "&s) else done;
release(bchan);
end;
bbuf←wordin(bchan);
end else errorstop("Sorry, binput not allowed in batch mode");
comment The basic input procedure getnext and its cousins gettok,getstring;
procedure page_end_error # gives error message when page ended unexpectedly;
if warning then
begin deletions_allowed←false # prevents possible recursion;
error("Input page ended while scanning "&pagewarning);
deletions_allowed←true;
end;
define curchar=⊂memory[location(curval),integer]⊃ # curchar ≡ curvalint;
integer nexttype # the type of the next token, when building constants;
simp procedure gettok # sends next low-level input token to curtype, curchar;
begin comment This procedure scans low-level tokens and also computes "nexttype"
(the type of the next low-level token) if the present low-level token might
be part of a constant that hasn't ended yet. Although a lot of cases
need to be handled, the inner loop is reasonably short and fast;
label switch;
switch: if recovery≥0 then
begin comment reading an external file;
label innerswitch;
innerswitch:if(curchar←lop(curbuf))then
case (curtype←chartype[curchar]) of begin
[space] go to innerswitch # ignore spaces;
[letter] begin integer c,s,l,firstfew; s←bitsperwd-bitsrem-5;
firstfew←(curchar land '37)lsh(bitsperwd-bitsrem-5); l←1;
while (c←chartype[curbuf])=letter or c=wxy do
begin l←l+1; s←s-5;
if s≥0 then firstfew←firstfew+((lop(curbuf)land '37)lsh s)
else c←lop(curbuf);
end;
curtype←ident; curchar←idlookup(firstfew,l) end;
[pnt] begin integer c; c←chartype[curbuf];
if c=pnt then
begin comment "..";
c←lop(curbuf); curtype←ddot;
end
else if c≠space and c≠carret then
begin comment decimal point or multiplication symbol;
if (nexttype←chartype[curbuf])≠digit then curtype←timesordiv;
end
else curtype←fullstop end;
[carret] begin curbuf←""; go to innerswitch end # move to next line;
[openq] begin curtype←char; curchar←lop(curbuf) end # quoted character;
[digit][apost] begin nexttype←chartype[curbuf];
if nexttype=pnt and chartype[curbuf[2 to 2]]≠digit then
nexttype←space end;
else comment do nothing;
end
else begin comment curbuf is empty, must go to next line of file;
if filename then
begin comment reading a character file;
integer p # temporary integer variable;
inbuf←input(recovery,crffbreak) #
read file up to carriage return or form feed;
if eof then
begin comment done with reading a file;
inbuf←null;
print(")");
release(recovery) # deactivate the channel;
popinput # restore previous status;
if pagewarning then page_end_error;
go to switch # keep scanning;
end;
if brchar=0 then
begin comment Input line more than 150 chars long;
print(nextline,
"Warning: Long input line has been broken.");
end;
if pausing and not_nonstop then
begin if inbuf='12 then p←lop(inbuf);
if length(inbuf)=1 then inbuf←" "&inbuf;
print(nextline);
IFWAITS ptostr(0,inbuf[1 to ∞-1]) # show inbuf on screen;
inbuf←inchwl&inbuf[∞ to ∞];
ELSEC outstr(inbuf[1 to ∞-1]);
begin string s; s←inchwl;
if s then inbuf←s&inbuf[∞ to ∞];
end;
ENDC
end;
if brchar='14 then
begin comment page mark;
p←field(info,loc)+1 # advance page number;
print(" ",p) # print progress report for user;
loc ← p lsh infod # reset line number to zero;
curbuf←inbuf←"";
if pagewarning then page_end_error;
end
else loc←loc+1 # advance line number;
comment No attempt is made here to remember the line
numbers on old style editing systems;
end
else if inptr then
begin comment done with line inserted during error routine;
popinput; go to switch;
end
else if not_nonstop then
begin comment reading online from terminal;
print(nextline,"*") # prompt user for input;
inbuf←inchwl&'15 # append carriage-return deleted by system;
setprint(null,"F");print(inbuf);setprint(null,"B") #
echo the input on ERRORS.TMP file for the record;
end
else begin comment nonstop mode, time to abort;
print(nextline,"*** (job aborted, no legal `end' found)");
quit;
end;
curbuf ← inbuf;
go to innerswitch;
end
end
else begin comment traversing a tokenlist;
if loc then
begin curtype←type(loc) # get type of token;
curchar←name(loc) # get char field of token;
loc←link(loc) # advance to next element of token list;
if loc then nexttype←type(loc) else nexttype←0;
end
else begin comment end of tokenlist;
popinput; go to switch;
end;
end
end;
simp integer procedure scanindex # scans an <index>, returns 0 if not found;
begin gettok;
if curtype=digit then
begin integer n; n←curchar-"0";
while nexttype=digit do
begin gettok; n←10*n+curchar-"0";
end;
return((n lsh infod)+curarea);
end;
if curtype=lpren then
begin comment look for "( index plus-or-minus constant )";
integer curindex;
gettok; if curtype≠ident or type(curchar)≠index then return(0);
curindex←vmemint(curchar); gettok;
if curtype=plusorminus then
begin integer n,s; s←curchar; n←0;
gettok;
while curtype=digit do
begin n←10*n+curchar-"0"; gettok;
end;
if s="+" then curindex←curindex+(n lsh infod)
else begin curindex←curindex-(n lsh infod);
if curindex<0 then return(0);
end;
end;
if curtype=rpren then return(curindex) else return(0);
end;
if curtype≠ident or type(curchar)≠index then return(0);
return(vmemint(curchar));
end;
internal simp procedure getnext # sends next high-level token to curtype, curval;
begin comment This procedure uses gettok to get the next high-level token
(combining constants and wxy-variables into single tokens);
real v,radix,scale; label realconst,intconst,finconst;
label tryagain # if at first you don't succeed, go back here;
tryagain: gettok;
case curtype of begin
[ident] begin curtype←type(curchar);
if curtype=innput then
begin inputfile; go to tryagain;
end;
curval←vmem[curchar] end;
[wxy] begin integer c,p; c←curchar land '37; p←scanindex;
if p then
begin p←wxylookup(c,p); curtype←type(p); curval←vmem[p];
end
else begin error((c+'140)&"-variable not followed by proper subscript");
curtype←known; curval←0;
end end;
[equals] if cond then curtype←rel;
[digit] begin v←curchar-"0"; radix←10; go to intconst end;
[apost] begin v←0; radix←8; go to intconst end;
[pnt] begin v←0; radix←10; go to realconst end;
else comment do nothing;
end;
return;
intconst: while nexttype=digit do
begin gettok; v←radix*v+curchar-"0";
end;
if nexttype≠pnt then go to finconst;
gettok;
realconst: scale←1.0;
while nexttype=digit do
begin gettok; scale←scale/radix; v←v+(curchar-"0")*scale;
end;
finconst: curtype←constant; curval←v;
end;
string curstring # string set by getstring;
simp procedure getstring(boolean uc) # sets curstring to next string in the input;
begin comment A quote mark has just been scanned. This procedure scans the
rest of the string, which is not allowed to contain quote marks or
carriage returns. If "uc" is true, lowercase letters are converted to uppercase;
integer c;
curstring←"";
while curbuf and (curbuf≠"""") and curbuf≠'15 do
begin c←lop(curbuf); if uc and c≥"a" and c≤"z" then c←c-("q"-"Q");
curstring←curstring&c;
end;
if curbuf="""" then c←lop(curbuf)
else error("String must end on the line where it begins");
end;
comment Dependency lists and the dumpdlist procedure;
comment The values of variables in METAFONT are defined implicitly by
linear equations, not directly by assignments. The METAFONT system handles
this by considering that the variable represented in node p has three
kinds of value depending on its type:
type(p) vmem(p)
known a real number
independent p
dependent pointer to linear combination of independents
For example, suppose we have the two equations
x1 - .2 x2 = .3 x3 - .1 x4:
x4 = 5.5.
Then x4 is known to have the value 5.5, and x1 is the linear combination
.2 x2 + .3 x3 - .55,
where x2 and x3 are independent. Such a representation can be maintained by
METAFONT in the following way: When a new equation α = β comes along, the
difference α - β is calculated as a linear combination of independent
variables, and this linear combination λ should be equated to zero.
If λ involves no independent variables, the equation is either redundant
or inconsistent, depending on whether the constant term is zero or nonzero.
If λ involves exactly one independent variable, we can solve for that
variable and its status changes to "known". This fact is used to simplify
all linear combinations involving that variable, and other variables might
therefore become known. Similarly, if λ involves two or more independent
variables, we choose one with the largest coefficient and let it depend on
the others, substituting this new linear combination where it appears in
other dependencies. Thus we reach a state where once again all the current
information is expressed in terms of known, independent, and dependent variables.
During the calculations with linear dependencies, a coefficient whose
magnitude is less than .0001 is regarded as zero. (This is reasonable since
the variables have values in units of pixels.)
Here's how a linear combination of independent variables is expressed as a
linked list: The linear combination α1v1 + ... + αkvk + β appears in k+1
two-word nodes, whose vmem fields respectively contain α1, ..., αk, and β.
The addresses of independent variables v1, ..., vk are assumed to be in
decreasing order, and these addresses appear in the info fields. The address
in the last node is zero. The link field in the first k nodes points to the
next node, while the link field in the last node is either zero or a
pointer to a dependent variable. Such a linked list is called a "dependency list,"
and the link in the last node is called its "final pointer."
(A more elaborate data structure could be
employed to avoid sequential searching during simplification, but in practice
the dependency lists are very short so this simple method seems adequate.)
If p points to a dependent variable, vmemint(p) points to the associated
dependency list. The program maintains an implicit list of all dependent variables:
mem[depvar] points to the first one, and the final pointer at the end of the first
one's dependency list points to the second one, etc.;
procedure dumpdlist(integer p) # prints dependency list pointed to by p;
begin comment Like dumplist, this procedure is extra-robust;
integer q,r;
q←p;
loop begin if q≥vmemsize then
begin print("???"); done;
end;
print(if vmem[q]≥0 then "+" else "-",cvf(abs(vmem[q])));
if (r←info(q))=0 then done;
print(" ",if r≥vmemsize then "BAD" else idname(r)," ");
q←link(q);
end;
end;
comment Operations on dependency lists: simpl,entersym,add,simplify,neweq,dsvalue;
integer procedure simpl(real v) # makes a dependency list of constant value v;
begin comment This procedure returns a pointer to a dependency list having
only a constant term, with value v;
integer p; getvavail(p); vmem[p]←v; mem[p]←0; return(p);
end;
internal procedure entersym(integer p) # called when a variable becomes known;
begin comment We are in proof mode and the interpreter has just changed the
variable in location \\p to "known" status;
integer q,r # pointer variables;
integer xco,yco # coordinates of new point;
string s # symbolic name of new point;
integer idn # numeric index;
s←idname(p);
case s of begin
["x"] r←field(link,vmemint(idarea)) # prepare to search $y$-list;
["y"] r←field(info,vmemint(idarea)) # prepare to search $x$-list;
else return
end # If not an $x$ or $y$ variable, we don't store it;
idn←name(p);
loop begin integer nn # temporary storage;
if type(r)=areahead then return;
if (nn←name(r))=idn then done;
if nn>idn then return;
r←link(r);
end;
if type(r)≠known then return;
comment Now both coordinates are defined;
if s="x" then
begin xco←xxtr*vmem[p]+xytr*vmem[r]+xtr;
yco←yxtr*vmem[p]+yytr*vmem[r]+ytr;
end
else begin xco←xxtr*vmem[r]+xytr*vmem[p]+xtr;
yco←yxtr*vmem[r]+yytr*vmem[p]+ytr;
end;
comment Now we search the tree;
proofins(xco+.5,yco+.5,s[2 to ∞]);
end;
define uinfo(p)=⊂ufield(info,mem[p])⊃ # info field of node p, not shifted right;
integer procedure add(integer p; real c; integer q) # forms p+cq, destroying p;
begin comment This procedure operates on two dependency lists, pointed to by
p and q, and it forms the dependency list corresponding to the linear combination
represented by p plus c times the linear combination represented by q. The
dependency list p is destructively modified while forming the new list, but
the dependency list q is not changed. The final pointer in the resulting
dependency list is the same as the final pointer in the original p list;
integer r,s,pp,qq; real v;
r←0 # mem[0] serves as temporary list head;
pp←uinfo(p); qq←uinfo(q) # pp,qq have this relation to p,q for efficiency;
loop if pp=qq then
begin vmem[p]←vmem[p]+c*vmem[q];
if pp=0 then done # stop when the constant terms are processed;
s←p; p←link(p); pp←uinfo(p); q←link(q); qq←uinfo(q) # advance p,q;
if abs(vmem[s])<0.0001 then freeavail(s)
else begin setlink(r,s); r←s;
end;
end
else if pp>qq then
begin setlink(r,p); r←p; p←link(p); pp←uinfo(p);
end
else begin v←c*vmem[q]; if abs(v)≥0.0001 then
begin getvavail(s); vmem[s]←v; mem[s]←mem[q];
setlink(r,s); r←s;
end;
q←link(q); qq←uinfo(q);
end;
setlink(r,p); return(mem[0]);
end;
integer procedure simplify(integer p,q,r) # simplifies p if variable q now equals r;
begin comment Given that p and r point to dependency lists, this procedure
returns a pointer to a dependency list equivalent to p but with r substituted for
q, if q occurs as an independent variable in p. List p may be destroyed in
the process, but list r remains unchanged;
integer s # pointer runs through list p;
integer ss # pointer that trails behind s;
integer qq # unshifted version of q (for efficiency's sake);
integer qqq # unshifted version of q+1;
real v # the coefficient of q;
ss←0; s←p; qq←q lsh infod; qqq←qq+(1 lsh infod);
while mem[s]≥qqq do
begin ss←s; s←link(s);
end;
if uinfo(s)≠qq then return(p) # variable q wasn't in the list;
v←vmem[s];
mem[0]←p; setlink(ss,link(s)) # take the node involving q out of the list;
freeavail(s) # and delete it;
return(add(mem[0],v,r)) # add v*r to the list;
end;
integer procedure neweq(integer lhs,rhs) # updates the variables given that lhs=rhs;
begin comment Here lhs and rhs point to dependency lists whose final pointer is 0.
This procedure changes one variable from independent to dependent, based on the
equation lhs=rhs, and then changes variables from dependent to known if this is
now possible. The output of this procedure is a dependency list, whose final
pointer is 0, and whose value is the common value of lhs and rhs. Lists lhs and
rhs are destroyed in the process;
integer p # points to dependency list being equated to 0;
integer q # |vmem[q]| is maximum over all coefficients in list p;
integer r # pointer runs through list p;
integer s # pointer that follows r;
real v # the maximum coefficient, vmem[q], before node q is destroyed;
real w # temp storage for new coefficient;
integer x # address of variable that becomes dependent;
p←add(lhs,-1.0,rhs) # compute lhs minus rhs, destroying lhs;
if mem[p]=0 then
begin comment There are no independent variables to define;
if vmem[p] then error("Inconsistent equation")
else error("Redundant equation");
freeavail(p); return(rhs) # the equation is effectively ignored;
end;
q←p; r←link(p);
while uinfo(r) do
begin if abs(vmem[r])>abs(vmem[q]) then q←r;
r←link(r);
end;
mem[0]←p; s←0; r←p; v←vmem[q]; x←info(q);
loop begin if r=q or abs(w←vmem[r]/v)<0.0001 then
begin comment delete node r from the list;
setlink(s,link(r)); freeavail(r); r←link(s);
end
else begin vmem[r]←-w; s←r; r←link(s);
end;
if uinfo(r)=0 then done;
end;
vmem[r]←-vmem[r]/v # adjust the constant term;
p←mem[0];
comment Now p points to the new dependency, and mem[r] is the final pointer;
if trdefs then
begin print(nextline,"### ",idname(x)," = "); dumpdlist(p);
end;
if mem[p]=0 then
begin comment variable x is now "known";
mem[x]←mem[x] xor ((known xor independent)lsh typed);
vmem[x]←vmem[p];
if symbolic then entersym(x);
r←depvar # prepare for simplification loop below;
end
else begin comment variable x is now "dependent";
mem[x]←mem[x] xor ((dependent xor independent)lsh typed);
vmemint(x)←p;
mem[r]←mem[depvar]; mem[depvar]←x;
end;
comment The following code is used to simplify all dependencies, now that variable
x is no longer independent. Now r will run through nodes in the list of
dependent variables, while q and s will be used for temporary storage;
while(s←mem[r])do
begin q←simplify(vmemint(s),x,p);
if uinfo(q)=0 then
begin comment The dependent variable s has become "known";
mem[s]←mem[s] xor ((known xor dependent)lsh typed);
vmem[s]←vmem[q];
mem[r]←mem[q]; freeavail(q);
if trdefs then print(nextline,"###### ",idname(s)," = ",vmem[s]);
if symbolic then entersym(s);
end
else begin comment Variable s remains dependent;
vmemint(s)←q;
do q←link(q) until uinfo(q)=0;
r←q;
end;
end;
q←simplify(rhs,x,p); if mem[p]=0 then freeavail(p); return(q);
end;
procedure dsvalue(integer p) # prepare to delete or redefine identifier node p;
begin case type(p) of begin
[dependent][independent] begin integer q,r,s;
error("Variable "&idname(p)&" never defined");
if type(p)=independent then
begin comment An independent variable is effectively set to one;
q←simpl(1.0);r←simpl(0.0);getvavail(s);mem[s]←r+(p lsh infod);vmem[s]←1.0;
q←neweq(s,q); freeavail(q);
end
else begin comment A dependent variable is removed from the dependency list;
q←depvar;
loop begin if mem[q]=p then done else if mem[q]=0 then confusion;
q←vmemint(mem[q]);
while uinfo(q) do q←link(q) # go to end of dependency list;
end;
r←vmemint(p);
while uinfo(r) do r←link(r);
mem[q]←mem[r]; mem[r]←0; dslist(vmemint(p));
end end;
[subroutine] dslist(vmemint(p)) # delete token list;
else comment do nothing;
end;
end;
comment Expression scanning routines: scanprimary, scanterm, scanexp, getexp;
procedure checkscalar(integer p; string s; real v) # ensure p is simple scalar;
begin comment This procedure gives error messages when a quantity that is
supposed to be scalar turns out to depend on other variables, and in that
case the value of v is substituted. Here p must point to a dependency list whose
final pointer is 0;
if mem[p] then
begin print(nextline,"! "); dumpdlist(p);
error("Undefined "&s&", replaced by "&cvf(v));
dslist(link(p)); mem[p]←0; vmem[p]←v;
end;
end;
integer procedure checkscanindex # scans and returns an index value;
begin integer i; if(i←scanindex) then return(i);
error("Improper index specification");
return(curarea) # 0 is assumed;
end;
real nsave # saved normaldeviate (we compute them two at a time);
simp real procedure normaldeviate # independent normal deviate with unit variance;
begin comment This procedure uses the "polar method" (Algorithm 3.4.1P);
real v1,v2,s,r;
if nsave then
begin r←nsave; nsave←0.0; return(r);
end;
loop begin v1←2*ran(seed)-1; v2←2*ran(0)-1; seed←0;
s←v1↑2+v2↑2;
if s<1.0 then done;
end;
r←sqrt(-2*log(s)/s); nsave←v1*r; return(v2*r);
end;
forward recursive integer procedure scanexp # scans and evaluates an <exp>;
forward recursive integer procedure scanterm # scans and evaluates a <term>;
recursive integer procedure scanprimary # scans and evaluates a <primary>;
begin comment This procedure scans the syntactic category called <primary>,
assuming that the first token already is in curtype and curval. Then it
returns a pointer to the dependency list represented by the primary.
Afterwards the token following the primary will have been scanned;
integer t # temp storage for dependency list to return;
case curtype of begin
[lpren] begin getnext; t←scanexp;
if curtype≠rpren then error("Right parenthesis substituted here") end;
[direction] begin integer i # index value; integer j # direction code;
integer p # pointer to simple expression; j←curvalint;
i←checkscanindex; p←wxylookup("w" land '37,i);
if type(p)=known then p←simpl(penadj(vmem[p],j))
else begin error("Undefined size w"&cvs(field(info,i))); p←simpl(0.0);
end;
getnext; t←add(scanterm,1.0,p); freeavail(p); return(t) end;
[unary] begin integer op # the unary operator; integer w # size, if needed;
op←curchar; if op=good then
begin integer j,p; j←checkscanindex # good<index><term>;
p←wxylookup("w" land '37,j);
if type(p)=known then w←vmem[p]+.5
else begin error("Undefined size w"&cvs(field(info,j))); w←1;
end;
end;
getnext; t←scanterm;
case op of begin
[root] begin checkscalar(t,"square root",0.0);
if vmem[t]<0 then begin error("Square root of "&cvf(vmem[t])&
", replaced by 0"); vmem[t]←0 end else vmem[t]←sqrt(vmem[t]) end;
[sine] begin checkscalar(t,"sine",0.0); vmem[t]←sind(vmem[t]) end;
[cosine] begin checkscalar(t,"cosine",0.0); vmem[t]←cosd(vmem[t]) end;
[round] begin checkscalar(t,"roundee",0.0); vmem[t]←floor(vmem[t]+.5) end;
[good] begin checkscalar(t,"goodee",0.0); if w land 1 then vmem[t] ←
floor(vmem[t]+.5) else vmem[t]←floor(vmem[t])+.5 end;
else confusion
end;
return(t) end # the next token has already been scanned;
[randm] t←simpl(normaldeviate);
[constant][known] t←simpl(curval);
[char] t←simpl(curchar);
[dependent] begin integer p,q,r # pointer variables for copying the list;
getvavail(p); t←p; q←curvalint;
loop begin vmem[p]←vmem[q]; mem[p]←uinfo(q);
if (mem[p]←uinfo(q))=0 then done;
getvavail(r); mem[p]←mem[p]+r; p←r; q←link(q);
end end;
[newid][independent] begin integer p; getvavail(t); getvavail(p);
if curtype=newid then
mem[curvalint]←mem[curvalint] xor ((independent xor newid)lsh typed);
vmem[t]←1.0; vmem[p]←0.0; mem[t]←(curvalint lsh infod)+p; mem[p]←0 end;
else begin error("You can't begin a ""primary"" like that"); t←simpl(0.0);
end
end;
getnext # scan the next token;
return(t);
end;
recursive integer procedure scanterm # scans and evaluates a <term>;
begin comment This procedure scans the syntactic category called <term>,
assuming that the first token already is in curtype and curval. Then it
returns a pointer to the dependency list represented by the term.
Afterwards the token following the term will have been scanned;
integer t # temp storage for dependency list to return;
t←scanprimary;
loop begin case curtype of begin
[lpren][char][constant][timesordiv][randm][known][direction][dependent]
[newid][independent][unary] begin integer lhs,rhs # operands in mult or div;
integer opchar # specifies multiplication or division;
if curtype≠timesordiv then opchar←"*"
else begin opchar←curchar; getnext;
end;
lhs←t; rhs←scanprimary;
if opchar="/" then
begin checkscalar(rhs,"divisor",1.0);
if vmem[rhs]=0.0 then
begin error("Division by 0"); vmem[rhs]←1.0;
end
else vmem[rhs]←1.0/vmem[rhs] # reduce division to multiplication;
end
else if mem[rhs] then
begin checkscalar(lhs,"factor",1.0);
lhs↔rhs;
end;
comment rhs is a scalar, multiply lhs by it;
t←add(simpl(0.0),vmem[rhs],lhs); freeavail(rhs); dslist(lhs) end;
[lbrack] begin comment <term>[<exp>,<exp>];
integer u,v # pointers to the expression values;
real alpha # the fraction;
getnext; u←scanexp;
if curtype≠comma then error("Comma substituted here");
getnext; v←scanexp;
if curtype≠rbrack then error("Right bracket substituted here");
getnext;
v ← add(v,-1.0,u) # set v ← v-u;
if mem[v] then checkscalar(t,"interval fraction",0.0) else v↔t;
alpha←vmem[t]; freeavail(t);
t ← add(u,alpha,v) # set t to desired result;
dslist(v) end;
else done
end;
end;
return(t);
end;
recursive integer procedure scanexp # scans and evaluates an <exp>;
begin comment This procedure scans the syntactic category called <exp>,
assuming that the first token already is in curtype and curval. Then it
returns a pointer to the dependency list represented by the exp.
Afterwards the token following the exp will have been scanned;
integer t # temp storage for dependency list to return;
case curtype of begin
[plusorminus] t←simpl(0.0);
[lpren][char][constant][randm][known][direction][dependent][newid][independent]
[unary] t←scanterm;
else begin error("You can't start an expression like that");
t←simpl(0.0); getnext;
end
end;
while curtype=plusorminus do
begin real pomo # plus or minus one; integer rhs # the righthand operand;
if curchar="+" then pomo←+1.0 else pomo←-1.0;
getnext; rhs←scanterm;
t←add(t,pomo,rhs); dslist(rhs);
end;
return(t);
end;
real procedure getexp # scans an expression and returns its value;
begin comment After calling this procedure, the token following the expression
has already been scanned;
integer p;
getnext; p←scanexp; checkscalar(p,"expression",0.0); freeavail(p); return(vmem[p]);
comment This uses the fact that freeavail doesn't clobber the value of vmem[p];
end;
comment The path scanning routine (scanpath);
comment Procedure "scanpath" is used to interpret and "draw" and "ddraw"
instructions. The syntax of paths is
[(<point>..)] <point> <..<point>>* [(..<point>)]
where <point> is
[|<exp>[#]|] <index> [{<exp>,<exp>}]
and |<exp>| denotes pen size, # denotes stability, {<exp>,<exp>} denotes a
tangent direction. Here <index> is either a constant or an index identifier
or an index identifier plus-or-minus a constant (NOT in parentheses).
The corresponding information, when there are n points in
the path, is stored in positions 0 to n+1 of the arrays listed below, and n is
stored in the global variable npts. Pen size and stability information are
not allowed in the paths for "ddraw";
internaldef maxpoints=20 # maximum number of points per path;
internal integer npts # number of points in current path;
internal saf integer array pointi[0:maxpoints+1] # index associated with a point;
internal saf real array pointw[0:maxpoints+2] # pen size at a point;
internal saf real array pointx[0:maxpoints+1] # x coordinate at a point;
internal saf real array pointy[0:maxpoints+1] # y coordinate at a point;
internal saf real array tanx,tany[0:maxpoints+1] # tangent direction at a point
(or (0,0) if METAFONT is to choose the tangent direction);
internal saf boolean array pointstab[0:maxpoints+1] # pen size should be stable
at the current point (i.e., the derivative should be zero);
internal saf integer array dpnti[0:maxpoints+1] # pointi for first path in ddraw;
internal saf real array dpntx,dpnty,dtanx,dtany[0:maxpoints+1] # pointx,pointy,
tanx,tany arrays for the first path in ddraw;
comment If the optional (<point>..) appears at the path's beginning, the
corresponding information is stored in position 0, otherwise pointi[0] is
set to -1. Information about the optional (..<point>) appearing at a path's end
is, similarly, stored in position npts+1;
boolean procedure scanpath(boolean ddrw) # scans paths to be drawn or ddrawn;
begin comment If the next input tokens don't specify a valid path, this
procedure returns "false". Otherwise it puts the path information into the
point arrays and returns "true", having already scanned the token that
immediately follows the path. Global variable cursize is updated to the last
specified pen size in a valid path;
label switch # go here to scan a token and branch to different cases;
label endpath # go here when the path is fully scanned;
boolean optend # the (..<point>) is present;
integer v # location of an x- or y-variable in memory;
integer i # loop index running from 0 to npts+1;
real pensize # current pen size;
npts←0; optend←false; pointi[0]←-1; pointw[1]←-1.0; pensize←cursize max 1.0;
comment The pointw entries are set temporarily to -1.0, a value that is reset
when an explicit size is specified;
switch: getnext; case curtype of begin
[lpren] if npts=0 then
begin npts←-1; pointw[0]←-1.0; go to switch;
end
else return(false);
[abbs] begin if ddrw then return(false);
if (pointw[npts+1]←getexp)<1.0 then
begin error("Pen size too small ("&cvf(pointw[npts+1])&
"), replaced by 1.0"); pointw[npts+1]←1.0;
end;
if curtype=hashmark then
begin pointstab[npts+1]←true; getnext;
end
else pointstab[npts+1]←false;
if curtype≠abbs then return(false); go to switch end;
[index] pointi[npts+1]←curvalint;
[constant] begin integer n; n←curval;
if n≠curval then return(false) # non-integer subscript;
pointi[npts+1]←(n lsh infod)+curarea end;
else return(false)
end;
comment An <index> has just been scanned, and its value is in pointi[npts+1].
However, the "plus-or-minus constant" has not yet been looked for;
getnext;
if curtype=plusorminus then
begin integer s,n; s←curchar; getnext;
if curtype≠constant then return(false);
n←curval; if n≠curval then return(false);
if s="+" then pointi[npts+1]←pointi[npts+1]+(n lsh infod)
else begin pointi[npts+1]←pointi[npts+1]-(n lsh infod);
if pointi[npts+1]<0 then return(false);
end;
getnext;
end;
if npts>maxpoints then overflow(maxpoints);
npts←npts+1; pointw[npts+1]←-1.0;
v←wxylookup("x" land '37, pointi[npts]);
if type(v)=known then pointx[npts]←vmem[v]
else begin error("Variable x"&indexname(pointi[npts])&
" is undefined, 0.0 assumed");
pointx[npts]←0.0;
end;
v←wxylookup("y" land '37, pointi[npts]);
if type(v)=known then pointy[npts]←vmem[v]
else begin error("Variable y"&indexname(pointi[npts])&
" is undefined, 0.0 assumed");
pointy[npts]←0.0;
end;
if curtype=lbrace then
begin tanx[npts]←getexp; if curtype≠comma then return(false);
tany[npts]←getexp; if curtype≠rbrace then return(false);
getnext;
end
else tanx[npts]←tany[npts]←0.0;
if optend then
begin if curtype≠rpren then return(false);
npts←npts-1; getnext; go to endpath;
end;
if curtype=ddot then
if npts=0 then
begin getnext;
if curtype=rpren then go to switch else return(false);
end
else go to switch;
if npts=0 then return(false);
if curtype=lpren then
begin optend←true; getnext;
if curtype≠ddot then return(false);
go to switch;
end;
comment The path has ended without the optional (..<point>);
if npts>maxpoints then overflow(maxpoints);
pointi[npts+1]←-1;
pointx[npts+1]←pointx[npts]; pointy[npts+1]←pointy[npts];
endpath: if npts≤0 then return(false);
if pointi[0]<0 then
begin comment The path began without the optional (<point>..);
pointx[0]←pointx[1]; pointy[0]←pointy[1]; pointw[0]←pointw[1];
end;
comment Now the arrays pointx[0:npts+1], pointy[0:npts+1], tanx[1:npts],
and tany[1:npts] are set properly for the "drawit" routine in MFRAST.
It remains to set up pointw[0:npts+1] and pointstab[1:npts],
for the cases when no pen size was specified;
for i←0 thru npts+1 do
if pointw[i]<0 then
begin pointw[i]←pensize; pointstab[i]←true;
end
else pensize←pointw[i];
return(true);
end;
internal procedure maincontrol # governs all the activities;
begin comment This procedure contains the master switch that causes all the
various pieces of METAFONT to do their things in the right order---unless
the user's input contains unexpected strangenesses. We have here the grand
climax of the program, the applications of all the tools that have been
so laboriously constructed. And it's also the messiest part of the program,
in the sense that it necessarily refers to other pieces of code all over the
place;
label beginstmt # go here in order to begin processing a command;
label mainswitch # like beginstmt, but first token of command has been scanned;
label endstmt # go here when you are done processing a command and curtype
should be semi or fullstop;
label finstmt # go here to call getnext and go to endstmt;
label flush # go here to ignore tokens until semi or fullstop or stop;
DEBUGONLY boolean checkingmem # trying to find out where memory assumptions die;
integer curtop # top of the auxiliary subroutine stack;
procedure flusherror(string s) # error causing current command to be flushed;
begin error(s&", command flushed"); go to flush;
end;
curarea←main; curtop←0 # set subroutine call stacks empty;
control←'400260 # points, modtrace, pagewarning, penreset;
clearpens(true) # initialize the pen memory;
forcednew←false # set normal state for identifier lookup;
maxvr←maxvs←4.0; minvr←minvs←0.5;
charclear # initialize charwd, charht, etc.;
epenxfactor←epenyfactor←1.0; excorr←eycorr←0.0;
xxtr←yytr←1.0; xytr←yxtr←xtr←ytr←0.0; safetyfactor←2.0 # parameters ← defaults;
hpenht←vpenwd←lpenht←rpenht←1;
magnification←1.0; rotation←0.0;
codingscheme←"UNSPECIFIED"; fontidentifier←"UNSPECIFIED";
designsize←10.0; fontfacebyte←0;
xresolution←yresolution←384/72.27 # current Dover settings;
dumpwindow←32; dumplength←1000; maxht←0;
IFWAITS seed←call(0,"ACCTIM") # date and time of day;
ELSEC seed←gtad # date and time of day; ENDC
nsave←0.0 # initialize the random-number generator;
DEBUGONLY checkingmem←false;
bbuf←0 # no binary input files open;
beginstmt:getnext;
DEBUGONLY if checkingmem then checkmem(false);
mainswitch: case curtype of begin
[quote] begin getstring(false); pagewarning←""""&curstring&"""";
if trtitles then print(nextline,curstring,"...");
if not maintitle then maintitle←curstring;
go to finstmt end;
[semi][fullstop] go to endstmt # empty command;
[lpren][char][constant][plusorminus][randm][known][direction][dependent][newid]
[independent][unary] begin integer lhs,rhs; lhs←scanexp;
if curtype=equals then
begin while curtype=equals do
begin getnext; rhs←scanexp; lhs←neweq(lhs,rhs);
end;
dslist(lhs); go to endstmt;
end;
if curtype=draw or curtype=ddraw then
begin if mem[lhs]=0 then
begin cursize←vmem[lhs]; freeavail(lhs);
end
else begin print(nextline,"! "); dumpdlist(lhs);
error("Undefined pen size"); dslist(lhs);
end;
go to mainswitch;
end;
print(nextline,"! "); dumpdlist(lhs); dslist(lhs);
flusherror("Missing = sign") end;
[penname] begin integer i; label nogood;
curpen←curvalint; curploc←0; cursize←0;
if curpen=spen then
begin comment special pen, we must scan a pen specification;
getnext;
if curtype=lpren then
begin for i←1 thru 7 do
begin spenspec[i]←getexp;
if (i<7 and curtype≠comma) or (i=7 and curtype≠rpren) then
go to nogood;
end;
getnext;
end;
end
else if curpen=epen then
begin getnext;
if curtype≠semi and curtype≠fullstop and curtype≠hashmark then
begin i←0; epen0←-1;
loop begin case curtype of begin
[hashmark][semi][fullstop] done;
[timesordiv] begin if curchar≠"." then go to nogood;
epen0←i end;
[lpren] begin i←i+1;
if i>epensize then overflow(epensize);
epenlspec[i]←getexp; if curtype≠comma then go to nogood;
epenrspec[i]←getexp; if curtype≠rpren then go to nogood;
if epenlspec[i]>epenrspec[i] then go to nogood end;
else go to nogood
end;
getnext;
end;
if epen0<0 then epen0←i;
epenptr←i;
end;
end
else getnext;
if curtype=hashmark then
begin getnext; eraser←true;
end
else eraser←false;
go to endstmt;
nogood: resetpens;
if curpen=spen then resetspen else resetepen;
flusherror("Improper pen specs");
end;
[subrtn] begin comment Scan and store a subroutine as a token list;
integer p # location of last token stored;
integer q # location to store the next token;
integer subname # address of subroutine name;
define store(t,n)=⊂begin getavail(q); mem[p]←mem[p]+q; p←q;
mem[p]←(t lsh typed)+(n lsh named) end⊃ # stores a token;
if pagewarning then error("Subroutine definition should follow "".""");
p←temphead; mem[p]←0 # temphead will point to the token list created;
gettok; if curtype≠ident then flusherror("No subroutine name");
subname←curchar # address of the subroutine name;
dsvalue(subname); setfield(type,mem[subname],subroutine);
store(ident,subname); pagewarning←"definition of "&idname(subname);
gettok;
while curtype≠colon do
begin label ng # go here if no good;
if (curtype=lpren) or (curtype=comma) then
begin getnext;
if curtype=varparam or curtype=indexparam then
begin integer d; d←curtype;
forcednew←true; gettok; forcednew←false;
if curtype=ident then
begin store(d,curchar);
setfield(type,mem[curchar],param);
gettok; if curtype=rpren then gettok;
continue;
end;
error("No parameter name"); go to ng;
end;
error("Should say var or index here"); go to ng;
end;
error("Should be ""("" or "","" or "":"" here");
ng: gettok;
end;
store(colon,":");
comment The preamble of the subroutine has now been scanned and stored;
loop begin comment Scanning the body of the subroutine;
gettok; case curtype of begin
[quote] begin getstring(false);
error("Titles are ignored inside subroutines"); continue end;
[ident] if type(curvalint)=innput then begin inputfile; continue end;
[stop] errorstop("Program ended while defining "&idname(subname));
[subroutine] begin error("Subroutines can't be defined inside subroutines");
continue end;
else comment In most cases we do nothing;
end;
store(curtype,curchar);
if curtype=fullstop then done;
end;
vmemint(subname)←mem[temphead];
p←link(mem[temphead]);
while type(p)≠colon do
begin comment Make the parameters invisible;
idhide(name(p)); p←link(p);
end;
pagewarning←""; go to beginstmt end;
[cawl] begin comment Calling a subroutine; integer c,p,q;
string s; label badcall;
define callerror(st)=⊂begin s←st; go to badcall end⊃;
getnext; if curtype=char then
begin c←curchar; if c<"a" or c>"z" then c←0; getnext;
end
else c←0;
if curtype≠subroutine then flusherror("Undefined subroutine");
p←curchar # points to token list for the subroutine;
if trcalls then print(nextline,"Calling ",idname(name(p)));
SHOWMEM if trcalls then print(" [",oneused,",",twoused,"]");
q←link(p); getnext;
while type(q)≠colon do
begin comment Matching arguments to parameters;
integer r # the parameter;
if curtype≠lpren then begin s←"Missing ""("""; go to badcall end;
r←name(q);
if type(r)≠param then callerror("Recursive call not allowed");
if type(q)=varparam then
begin vmem[r]←getexp;
if trcalls then print("(",cvf(vmem[r]),")");
mem[r]←mem[r] xor ((known xor param)lsh typed);
end
else begin comment Now type(q)=indexparam;
if (vmemint(r)←scanindex) then
begin mem[r]←mem[r]xor((index xor param)lsh typed);
if trcalls then print("(",indexname(vmemint(r)),")");
end
else callerror("Improper index argument");
getnext # scan the token following the index;
end;
q←link(q);
if curtype=rpren then getnext
else if curtype=comma then curtype←lpren
else callerror("Missing punctuation");
end;
comment The arguments have been scanned;
if curtype≠semi and curtype≠fullstop then callerror("Improper call");
pushinput; loc←link(q); recovery←-p;
getvavail(q); mem[q]←(areahead lsh typed)+(c lsh named)+curarea;
vmemint(q)←(q lsh infod)+q # null lists of x- and y-variables;
curarea←q;
comment Now we put curtype, control, curpen, and cursize onto an auxiliary
stack whose pointer is curtop, so that these can be restored properly when
the subroutine call is concluded;
getvavail(q); mem[q]←curtop+(curtype lsh infod); vmemint(q)←control;
getvavail(p); mem[p]←q+(curpen lsh infod); vmem[p]←cursize;
if eraser then mem[p]←mem[p]+('1000 lsh infod);
curtop←p;
if penreset then resetpens;
go to beginstmt;
badcall: q←link(p);
while type(q)≠colon do
begin setfield(type,mem[name(q)],param) # reset parameters;
q←link(q);
end;
flusherror(s) end;
[new] begin
loop begin label done_with_entry # go here when item is processed;
gettok;
if curtype≠ident then
begin if curtype=wxy
then begin integer c; c←curchar land '37; curchar←scanindex;
if curchar then curchar←wxylookup(c,curchar)
else begin error((c+'140)&"-variable not followed by"&
" proper subscript"); go to done_with_entry;
end;
end
else begin error("Improper name"); go to done_with_entry;
end;
end;
dsvalue(curchar); setfield(type,mem[curchar],newid);
vmemint(curchar)←curchar;
done_with_entry:
getnext; if curtype≠comma then done;
end;
go to endstmt end;
[mfparam] begin integer n; n←curchar;
if n≤realpars then realparam[n]←getexp else
if n≤stringpar then
begin integer oldval; oldval←intparam[n];
intparam[n]←getexp+.5;
if n≤penparam and intparam[n]≠oldval then
begin clearpens(false) # hpenht or vpenwd or
lpenht or rpenht change => pens must change too;
if intparam[n]<1 then
begin error(sympar[n]&" too small, set to 1");
intparam[n]←1;
end;
end;
end
else begin
getnext # to flush the starting quote;
if curtype≠quote then flusherror("Title expected");
getstring(true);
stringparam[n]←curstring;
getnext # to read in the semicolon;
end;
go to endstmt end;
[break] begin integer b,j,k,xy; k←curchar;
b←getexp/k+.5; b←k*b # round to nearest multiple of k;
if k=10 then xy←1 else xy←0 # k=10 means crsybreak, otherwise crsxbreak;
j←brkptr[xy]; while b<brktab[xy,j] do j←j-1;
if b≠brktab[xy,j] then
begin if brkptr[xy]=brksize then overflow(brksize);
k←brkptr[xy]; brkptr[xy]←k+1;
while k>j do
begin brktab[xy,k+1]←brktab[xy,k]; k←k-1;
end;
brktab[xy,j+1]←b;
end;
go to endstmt end;
[contrl] begin control←control lor curvalint;
if curvalint=circlemodebit then clearpens(false);
go to finstmt end;
[no] begin getnext; if curtype≠contrl then flusherror("Unknown control code");
control←control land (lnot curvalint);
if curvalint=circlemodebit then clearpens(false);
go to finstmt end;
[iff] begin integer lhs, rhs, t, unbal; boolean b; label badif;
cond←true; getnext; lhs←scanexp; cond←false;
if curtype≠rel then
begin error("Missing relation"); go to badif;
end;
t←curchar # t identifies the relation;
if t>"≠"+2 then t←t-("<"-("≠"+3)) # assumes consecutive 7-bit codes ≠≤≥ and <=>;
getnext; rhs←scanexp;
if curtype≠colon then
begin error("Missing "":"""); dslist(rhs); go to badif;
end;
lhs←add(lhs,-1.0,rhs); dslist(rhs);
if mem[lhs] then
begin print(nextline,"! "); dumpdlist(lhs);
error("Indeterminate relation"); go to badif;
end;
b←case t-"≠" of (vmem[lhs]≠0.0, vmem[lhs]≤0.0, vmem[lhs]≥0.0, vmem[lhs]<0.0,
vmem[lhs]=0.0, vmem[lhs]>0.0);
freeavail(lhs);
if b then go to beginstmt;
comment The relation is false, skip over the code;
unbal←0; noinput←true;
loop begin getnext; case curtype of begin
[quote] getstring(false);
[iff] unbal←unbal+1;
[elsse] if unbal=0 then done;
[ffi] if unbal=0 then
begin noinput←false; go to finstmt;
end
else unbal←unbal-1;
[stop][fullstop] begin error("Routine ended in skipped conditional text");
noinput←false; go to endstmt end;
else comment do nothing;
end;
end;
comment The matching else has been found;
noinput←false; gettok; if curtype≠colon then
begin error("Missing colon inserted"); go to mainswitch;
end;
go to beginstmt;
badif: dslist(lhs); go to beginstmt end;
[elsse] begin comment The else branch of a conditional will be skipped;
integer unbal; unbal←0; noinput←true;
loop begin getnext; case curtype of begin
[quote] getstring(false);
[stop][fullstop] begin error("Routine ended in skipped conditional text");
noinput←false; go to endstmt end;
[iff] unbal←unbal+1;
[ffi] if unbal=0 then done else unbal←unbal-1;
else comment do nothing;
end;
end;
noinput←false; go to finstmt end;
[ffi] go to finstmt # fi when encountered normally is a no-op;
[binput] begin while bbuf≤0 do binopen; binin; go to finstmt end;
[draw] begin if scanpath(false) then drawit(false) else flusherror("Bad path");
go to endstmt end;
[ddraw] begin integer i # temporary variable used to copy point information;
integer dnpts # number of points on first path;
if not scanpath(true) then flusherror("Bad path");
if curtype≠comma then flusherror("Missing "",""");
for i←0 thru npts+1 do
begin dpnti[i]←pointi[i]; dpntx[i]←pointx[i]; dpnty[i]←pointy[i];
dtanx[i]←tanx[i]; dtany[i]←tany[i];
end;
dnpts←npts;
if not scanpath(true) then flusherror("Bad path");
if npts≠dnpts then flusherror("Paths don't match up");
drawit(true); go to endstmt end;
[varchar] begin integer acc; acc←0;
isvarchar←true;
loop begin integer i; i←getexp+.5; acc←(acc lsh 8)+i;
if curtype≠comma then done;
end;
varchardata←acc;
go to endstmt end;
[charlist] if needchecksum then begin integer i; label nogood;
tfminit; i←getexp+.5;
if i<0 or i>'177 then go to nogood;
loop begin integer i1;
if curtype≠comma then go to endstmt;
i1←getexp+.5;
comment Old MF would do this: if i1=0 then go to endstmt;
case field(tg,tfmdir[i]) of begin
[tagnone] ;
[taglig][taglist] flusherror("Duplicate ligature/charlist entry");
[tagvar] flusherror("Varchar can't be in the middle of a charlist")
end;
if i1<0 or i1>'177 then go to nogood;
tfmdir[i]←tfmdir[i] xor ((taglist xor tagnone) lsh tgd);
tfmdir[i]←tfmdir[i]+(i1 lsh remd);
i←i1;
end;
nogood: flusherror("Improper charlist entry") end else go to flush;
[texinfo] if needchecksum then begin tfminit;
loop begin tfmptr←tfmptr+1;
if tfmptr>tfmparsize then flusherror("Too much texinfo");
tfmpars[tfmptr]←getexp;
if curtype≠comma then done;
end;
go to endstmt end else go to flush;
[lig] if needchecksum then begin integer i; label nogood;
tfminit;
loop begin integer p;
getnext; if curtype=semi or curtype=fullstop then go to endstmt;
p←scanexp; checkscalar(p,"character code",0.0); freeavail(p);
i←vmem[p]+.5;
if i<0 or i>'177 then go to nogood;
if nlg≥lgmsk then
flusherror("Too many ligatures/kerns");
if curtype=colon then
begin
case field(tg,tfmdir[i]) of begin
[tagnone] ;
[taglig][taglist] flusherror("Duplicate ligature/charlist entry");
[tagvar] flusherror("Varchar can't have ligature/kern")
end;
tfmdir[i]←tfmdir[i] xor ((tagnone xor taglig) lsh tgd);
tfmdir[i]←tfmdir[i]+((nlg+1) lsh remd);
end
else if curtype=equals then
begin integer j;
j←getexp+.5;
if j<0 or j>'177 then go to nogood;
nlg←nlg+1;
tfmlg[nlg]←((((i lsh 1)lor ligstep)lsh 15)lor j);
if curtype≠comma then done;
end
else if curtype=kern then
begin integer j;
tfmkr[nkr+1]←getexp # no need to check for overflow,
since every kern takes up a lig/kern program step;
j←0; while tfmkr[j]≠tfmkr[nkr+1] do j←j+1;
if j>nkr then nkr←j;
nlg←nlg+1;
tfmlg[nlg]←((((i lsh 1)lor kernstep)lsh 15)lor j);
if curtype≠comma then done;
end
else go to nogood;
end;
tfmlg[nlg]←tfmlg[nlg] lor (1 lsh 31); go to endstmt;
nogood: flusherror("Improper ligature/kern entry") end else go to flush;
[invisible] if symbolic then
begin integer xco,yco; xco←getexp;
if curtype≠comma then flusherror("Missing "",""");
yco←getexp;
proofins(xco+.5,yco+.5,"");
go to endstmt;
end
else go to flush;
[stop] begin if pagewarning then print(nextline,"(end occurred within ",
pagewarning,")");
return end # this is how the maincontrol procedure should end;
else flusherror("You can't begin a statement like that")
end;
finstmt: getnext;
endstmt: if curtype=semi then go to beginstmt;
if curtype=fullstop then
begin comment End of a main routine or subroutine;
integer p,q;
p←field(info,vmemint(curarea)) # delete x-variables;
while type(p)≠areahead do
begin dsvalue(p); p←link(p);
end;
p←field(info,vmemint(curarea)); setfield(info,vmemint(curarea),curarea);
while type(p)≠areahead do
begin q←link(p); freeavail(p); p←q;
end;
comment This cumbersome two-pass method for deletion is necessary because
dsvalue may call idname, which requires well-formed xy-lists;
p←field(link,vmemint(curarea)) # delete y-variables;
while type(p)≠areahead do
begin dsvalue(p); p←link(p);
end;
p←field(link,vmemint(curarea)); setfield(link,vmemint(curarea),curarea);
while type(p)≠areahead do
begin q←link(p); freeavail(p); p←q;
end;
if curarea≠main then
begin comment End of a subroutine;
integer p;
if trcalls then print(nextline,"Leaving ",idname(name(-recovery)));
SHOWMEM if trcalls then print(" [",oneused,",",twoused,"]");
p←link(-recovery); while type(p)≠colon do
begin setfield(type,mem[name(p)],param) # reset params;
p←link(p);
end;
p←curarea; curarea←link(curarea); freeavail(p);
p←curtop;
cursize←vmem[p]; curpen←info(p) land '777; eraser←info(p) land '1000;
curploc←0;
p←link(p);
freeavail(curtop);
curtype←info(p); control←vmemint(p); curtop←link(p);
freeavail(p);
popinput; go to endstmt;
end;
comment End of a main routine;
finishchar # output the drawing to a font file if appropriate;
charclear # reinitialize the character parameters to default values;
pagewarning←"" # no error to encounter file pages now;
if penreset then resetpens;
go to beginstmt;
end;
error("Extra code at end of command will be flushed");
flush: while curtype≠semi and curtype≠fullstop and curtype≠stop do getnext;
if curtype=fullstop then go to endstmt else go to beginstmt;
end;
end